/*
* Copyright 2010-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build.aws.maven;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.maven.wagon.ResourceDoesNotExistException;
import org.apache.maven.wagon.TransferFailedException;
import org.apache.maven.wagon.authentication.AuthenticationException;
import org.apache.maven.wagon.authentication.AuthenticationInfo;
import org.apache.maven.wagon.proxy.ProxyInfoProvider;
import org.apache.maven.wagon.repository.Repository;
import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
import com.amazonaws.regions.DefaultAwsRegionProviderChain;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.internal.Mimetypes;
import com.amazonaws.services.s3.model.ListObjectsRequest;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectSummary;
/**
* An implementation of the Maven Wagon interface that allows you to access the Amazon S3 service. URLs that reference
* the S3 service should be in the form of <code>s3://bucket.name</code>. As an example
* <code>s3://static.springframework.org</code> would put files into the <code>static.springframework.org</code> bucket
* on the S3 service.
* <p/>
* This implementation uses the <code>username</code> and <code>passphrase</code> portions of the server authentication
* metadata for credentials.
*/
public final class PrivateS3Wagon extends AbstractWagon {
private static final String KEY_FORMAT = "%s%s";
private static final String RESOURCE_FORMAT = "%s(.*)";
private volatile AmazonS3 amazonS3;
private volatile String bucketName;
private volatile String baseDirectory;
/**
* Creates a new instance of the wagon
*/
public PrivateS3Wagon() {
super(true);
}
PrivateS3Wagon(AmazonS3 amazonS3, String bucketName, String baseDirectory) {
super(true);
this.amazonS3 = amazonS3;
this.bucketName = bucketName;
this.baseDirectory = baseDirectory;
}
@Override
protected void connectToRepository(Repository repository, AuthenticationInfo authenticationInfo, ProxyInfoProvider proxyInfoProvider)
throws AuthenticationException {
if (this.amazonS3 == null) {
ClientConfiguration clientConfiguration = S3Utils.getClientConfiguration(proxyInfoProvider);
this.bucketName = S3Utils.getBucketName(repository);
this.baseDirectory = S3Utils.getBaseDirectory(repository);
if (authenticationInfo == null) {
this.amazonS3 = new AmazonS3Client(new DefaultAWSCredentialsProviderChain(), clientConfiguration);
} else {
AWSCredentials awsCredentials = new AuthenticationInfoAWSCredentials(authenticationInfo);
this.amazonS3 = new AmazonS3Client(awsCredentials, clientConfiguration);
}
try {
com.amazonaws.regions.Region region = parseRegion(new DefaultAwsRegionProviderChain().getRegion());
if (!region.getPartition().equals("aws")) {
this.amazonS3.setRegion(region);
} else {
detectEndpointFromBucket();
}
} catch (AmazonClientException e) {
detectEndpointFromBucket();
}
}
}
private void detectEndpointFromBucket() {
String location = this.amazonS3.getBucketLocation(this.bucketName);
try {
Region region = Region.fromLocationConstraint(this.amazonS3.getBucketLocation(this.bucketName));
this.amazonS3.setEndpoint(region.getEndpoint());
} catch (IllegalArgumentException e) {
this.amazonS3.setRegion(parseRegion(location));
}
}
private com.amazonaws.regions.Region parseRegion(String region) {
return com.amazonaws.regions.Region.getRegion(Regions.fromName(region));
}
@Override
protected void disconnectFromRepository() {
this.amazonS3 = null;
this.bucketName = null;
this.baseDirectory = null;
}
@Override
protected boolean doesRemoteResourceExist(String resourceName) {
try {
getObjectMetadata(resourceName);
return true;
} catch (AmazonServiceException e) {
return false;
}
}
@Override
protected boolean isRemoteResourceNewer(String resourceName, long timestamp) throws ResourceDoesNotExistException {
try {
Date lastModified = getObjectMetadata(resourceName).getLastModified();
return lastModified == null ? true : lastModified.getTime() > timestamp;
} catch (AmazonServiceException e) {
throw new ResourceDoesNotExistException(String.format("'%s' does not exist", resourceName), e);
}
}
@Override
protected List<String> listDirectory(String directory) throws ResourceDoesNotExistException {
List<String> directoryContents = new ArrayList<String>();
try {
String prefix = getKey(directory);
Pattern pattern = Pattern.compile(String.format(RESOURCE_FORMAT, prefix));
ListObjectsRequest listObjectsRequest = new ListObjectsRequest() //
.withBucketName(this.bucketName) //
.withPrefix(prefix) //
.withDelimiter("/");
ObjectListing objectListing;
objectListing = this.amazonS3.listObjects(listObjectsRequest);
directoryContents.addAll(getResourceNames(objectListing, pattern));
while (objectListing.isTruncated()) {
objectListing = this.amazonS3.listObjects(listObjectsRequest);
directoryContents.addAll(getResourceNames(objectListing, pattern));
}
return directoryContents;
} catch (AmazonServiceException e) {
throw new ResourceDoesNotExistException(String.format("'%s' does not exist", directory), e);
}
}
@Override
protected void getResource(String resourceName, File destination, TransferProgress transferProgress) throws TransferFailedException,
ResourceDoesNotExistException {
InputStream in = null;
OutputStream out = null;
try {
S3Object s3Object = this.amazonS3.getObject(this.bucketName, getKey(resourceName));
in = s3Object.getObjectContent();
out = new TransferProgressFileOutputStream(destination, transferProgress);
IoUtils.copy(in, out);
} catch (AmazonServiceException e) {
throw new ResourceDoesNotExistException(String.format("'%s' does not exist", resourceName), e);
} catch (FileNotFoundException e) {
throw new TransferFailedException(String.format("Cannot write file to '%s'", destination), e);
} catch (IOException e) {
throw new TransferFailedException(String.format("Cannot read from '%s' and write to '%s'", resourceName, destination), e);
} finally {
IoUtils.closeQuietly(in, out);
}
}
@Override
protected void putResource(File source, String destination, TransferProgress transferProgress) throws TransferFailedException,
ResourceDoesNotExistException {
String key = getKey(destination);
InputStream in = null;
try {
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(source.length());
objectMetadata.setContentType(Mimetypes.getInstance().getMimetype(source));
in = new TransferProgressFileInputStream(source, transferProgress);
this.amazonS3.putObject(new PutObjectRequest(this.bucketName, key, in, objectMetadata));
} catch (AmazonServiceException e) {
throw new TransferFailedException(String.format("Cannot write file to '%s'", destination), e);
} catch (FileNotFoundException e) {
throw new ResourceDoesNotExistException(String.format("Cannot read file from '%s'", source), e);
} finally {
IoUtils.closeQuietly(in);
}
}
private ObjectMetadata getObjectMetadata(String resourceName) {
return this.amazonS3.getObjectMetadata(this.bucketName, getKey(resourceName));
}
private String getKey(String resourceName) {
return String.format(KEY_FORMAT, this.baseDirectory, resourceName);
}
private List<String> getResourceNames(ObjectListing objectListing, Pattern pattern) {
List<String> resourceNames = new ArrayList<String>();
for (String commonPrefix : objectListing.getCommonPrefixes()) {
resourceNames.add(getResourceName(commonPrefix, pattern));
}
for (S3ObjectSummary s3ObjectSummary : objectListing.getObjectSummaries()) {
resourceNames.add(getResourceName(s3ObjectSummary.getKey(), pattern));
}
return resourceNames;
}
private String getResourceName(String key, Pattern pattern) {
Matcher matcher = pattern.matcher(key);
if (matcher.find()) {
return matcher.group(1);
}
return key;
}
}